/*
 * BaseStation.java
 */

package org.sunspotworld.demo;

import com.sun.spot.io.j2me.radiogram.Radiogram;
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.util.Utils;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import com.sun.spot.peripheral.TimeoutException;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;

/**
 * @author mi zhang
 */
public class BaseStation implements SensorTypes, ProtocolParameter{

    /**
     * Local Variables.
     */
    //
    private MainFrame myGUI;
    //
    private Vector mySPOTLIST = new Vector();                  // Vector itself is synchronized
    //
    private BitMap PORTBitMap = new BitMap(MAX_SPOT_NUMBER);   // Shared Variable. Need synchronization
    // Thread Variables
    private Thread broadcastheartbeatThread = null;
    private Thread heartbeatListenerThread = null;
    // Loop Control
    private boolean broadcastDo = false;
    private boolean heartbeatListenerDo = false;
    //
    private static final int MAX_SPOT_NUMBER        = 10;

    /**
     * =========================================================================
     */

    /**
     * Pause for a specified time.
     *
     * @param time the number of milliseconds to pause
     */
    public void pause (long time) {
        try {
            Thread.currentThread().sleep(time);
        } catch (InterruptedException ex) {
            System.err.println("Caught " + ex + " while pausing.");
        }
    }

    /**
     * Initialize any needed variables.
     */
    public void initialize() {
        IRadioPolicyManager rpm = RadioFactory.getRadioPolicyManager();
        rpm.setChannelNumber(channel);
        rpm.setPanId(PAN_ID);
        rpm.setOutputPower(power - 32); // "power = 0" is the maximum power allowed for SPOT
        pause(SECOND);
    }

    /**
     * "Heartbeat" Packets Broadcaster
     */
    public void broadcastHeartbeatLoop () {
        System.out.println("Thread_BHBL: Begins.");
        RadiogramConnection txConn = null;
        Datagram xdg = null;
        String ourAddress = System.getProperty("IEEE_ADDRESS");
        System.out.println("Starting server on " +
                ourAddress + " ...");
        broadcastDo = true;
        int debug_counter = 0;
        try {
        /**
         * Step1: Open a Broadcast connection to broadcast the Heartbeat packets.
         */
            txConn = (RadiogramConnection) Connector.open("radiogram://broadcast:" + HEARTBEAT_BROADCAST_PORT); // Broadcast !
            xdg = (Radiogram)txConn.newDatagram(txConn.getMaximumLength());
        } catch (IOException ex) {
            System.err.println("Thread_BHBL: Caught " + ex + " in txConn connection initialization in BS");
            ex.printStackTrace();
            //System.exit(1);
        }
        /**
         * Step2: Broadcast the Heartbeat packets.
         */
        while (broadcastDo) {
            try {
                long now = System.currentTimeMillis();
             // Package the HEARTBEAT datagram and send it.
                debug_counter++;
                xdg.reset();
                xdg.writeByte(HEARTBEAT_PACKET);
                txConn.send(xdg);
                System.out.println("Loop_BHBL: " + debug_counter + " Broadcast packet sent.");
             // Go to sleep to save power
                if (HEARTBEAT_PERIOD - (System.currentTimeMillis() - now) >= 0)
                    Utils.sleep(HEARTBEAT_PERIOD - (System.currentTimeMillis() - now));
            } catch (Exception ex) {
                System.err.println("Thread_BHBL: Caught " + ex + " while broadcasting Heartbeat packets in BS.");
                ex.printStackTrace();
            }
        }
    }

    /**
     * "Heartbeat" Packets Receiver
     *      A. Port Number Assignment
     *      B. Generate corresponding Listeners
     */
    public void heartbeatListenerLoop () {
        System.out.println("Thread_HBLL: Begins.");
        int debug_counter = 0;
        RadiogramConnection rcvConn = null;
        Datagram rdg = null;
        heartbeatListenerDo = true;
        boolean isInTheLIST = false;
        int nothing = 0;
        try {
        /**
         * Step1: Open a server connection to receive the ACK packets.
         */
            rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + HEARTBEAT_BROADCAST_PORT); // Server !
            rcvConn.setTimeout(TIMEOUT_PERIOD);
            rdg = rcvConn.newDatagram(rcvConn.getMaximumLength());
        /**
         * Step2: Book-keeping the Free-range SunSPOTs.
         */
            while (heartbeatListenerDo) {
               try {
                    debug_counter++;
                    System.out.println("Loop_HBLL: " + debug_counter + " starts.");
                    rdg.reset();
                    rcvConn.receive(rdg);   // Blocking
                    String SPOTAddress = rdg.getAddress();  // Incoming SPOT's MAC Id
                    int myPort = -1;
                    System.out.println("Loop_HBLL: " + debug_counter + " Incoming ACK packet address: " + SPOTAddress + " ...");
                    byte packetType = rdg.readByte();
                    if (packetType == ACK_PACKET) {
                        System.out.println("Loop_HBLL: " + debug_counter + " is ACK_PACKET.");
                        nothing = 0;    // Reset
                        /**
                         * 1. Check if the LIST contains this SunSPOT.
                         *      a) if there is NOT, insert the new SPOT into the LIST.
                         *      b) if there is, do nothing
                         */
                        isInTheLIST = false;
                        //TODO- update Listener to handle mySPOTList
                        //synchronized (mySPOTLIST) {
                            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
                            while (enu.hasMoreElements()) {
                                SingleSPOT next = (SingleSPOT) enu.nextElement();
                                //System.out.println("Loop: " + debug_counter + " next.getAddress()=." + next.getAddress());
                                //System.out.println("Loop: " + debug_counter + " SPOT MAC: " + next.getAddress() + " SPOT HP: " + next.getHP());
                               if (next.getAddress().equals(SPOTAddress)) {
                                   System.out.println("Loop_HBLL: " + debug_counter + " Address Matches!.");
                                   isInTheLIST = true;
                                   break;
                               } else { // We DONT remove any spots here
                               }
                            }
                         /**
                          * If this is a new SPOT,
                          *      a) Find an available port in the BitMap
                          *      b) Generate a new Listener
                          *      c) Register the new SPOT into the LIST
                          */
                            if (isInTheLIST == false) {
                                System.out.println("Loop_HBLL: " + debug_counter + " Find New SPOT ! ");
                                // a) Find an available port
                                synchronized (PORTBitMap) {
                                    myPort = PORTBitMap.Find();
                                }
                                if (myPort == -1) {
                                    System.out.println("Loop_HBLL: " + debug_counter + " No free radio ports available now ");
                                    // Enlarge the total length of the BipMap dynamically ??? -- Right now NO
                                }
                                else {
                                    System.out.println("Loop_HBLL: " + debug_counter + " New Port: " + myPort);
                                    myPort = myPort + 60;   // 0 to 31 are reserved for system use
                                // b) Generate a new Listener
                                    // Generate a new Listener
                                    // Pass the SPOTLIST to each Listener
                                    Listener gListener = new Listener(Integer.toString(myPort), mySPOTLIST, PORTBitMap, myGUI);
                                    // Start the Listener
                                    // ONLY initiate the connection. DO NOT start transmission
                                    gListener.start();
                                    System.out.println("Loop_HBLL: " + debug_counter + " A new Listener starts");
                                // c) Register the new SPOT into the LIST
                                    //TODO - add to the list only after the new port unicast connection is successful.
                                    SingleSPOT newMember = new SingleSPOT(SPOTAddress, myPort, gListener);
                                    //SingleSPOT newMember = new SingleSPOT(SPOTAddress, myPort);
                                    mySPOTLIST.addElement(newMember);
                                    System.out.println("Loop_HBLL: " + debug_counter + " The New SPOT is added into the mySPOTLIST");
                                // d) Send the new port number to FR
                                    try {
                                        RadiogramConnection txConn = (RadiogramConnection) Connector.open("radiogram://"+ SPOTAddress +":" + HEARTBEAT_BROADCAST_PORT);
                                        Datagram xdg = (Radiogram)txConn.newDatagram(txConn.getMaximumLength());
                                        xdg.reset();
                                        xdg.writeByte(ASSIGNPORT_PACKET);
                                        xdg.writeInt(myPort);
                                        txConn.send(xdg);
                                        txConn.close();
                                        System.out.println("Loop_HBLL: " + debug_counter + " Send the new port number to FR");
                                    } catch (Exception ex) {
                                        System.err.println("Loop_HBLL: Caught " + ex + " in txConn connection initialization.");
                                        ex.printStackTrace();
                                    //System.exit(1);
                                    }
                                }
                            }
                            System.out.println("Loop_HBLL: " + debug_counter + " mySPOTLIST.size() = " + mySPOTLIST.size());
                        //}
                    } else {
                        /* Ignore other kinds of packets */
                        System.out.println("Loop_HBLL: " + debug_counter + " NOT ACK_PACKET.");
                    }
                } catch (TimeoutException tex) {        // Timeout
                    System.out.println("Loop_HBLL: " + debug_counter + " Timeout.");
                    nothing++;
                    System.out.println("Loop_HBLL: " + debug_counter + " nothing = " + nothing);
                    //long now = System.currentTimeMillis();
                    //System.out.println("Loop: " + debug_counter + " now = " + now);
                    if (nothing >= MAX_MISSED_PACKETS) {
                        System.out.println("Loop_HBLL: " + debug_counter + " Can NOT find any Clients.");
                     // Go to deep sleep -- Save power
                        Utils.sleep(DEEP_SLEEP_TIME);
                    }
                }
            }
      } catch (Exception ex) {
         System.err.println("Loop_HBLL: Caught " + ex + " in rcvConn connection initialization.");
         ex.printStackTrace();
         //System.exit(1);
      }
    }
    
    /**
     * Default BaseStation Constructor
     * Called when we start a new session
     * 0. Initialization
     * 1. Spawn a thread to broadcast "Heartbeat" packets.
     * 2. Spawn a new thread to listen to "Heartbeat" packets.
     *      A. Port Number Assignment
     *      B. Generate corresponding Listeners
     * 3. myGUI has the MainGUI object
     */
    public BaseStation(MainFrame objGUI){

        this.myGUI = objGUI;
     // 0. Initialization
        initialize();
     // 1. Spawn a thread to broadcast "Heartbeat" packets.
        broadcastheartbeatThread = new Thread() {
            @Override
            public void run () {
                broadcastHeartbeatLoop();
            }
        };                      
        broadcastheartbeatThread.start();
     // 2. Spawn a new thread to listen to "Heartbeat" packets.
     //     A. Port Number Assignment
     //     B. Generate corresponding Listeners
        heartbeatListenerThread = new Thread() {
            @Override
            public void run () {
                heartbeatListenerLoop();
            }
        };                      
        heartbeatListenerThread.start(); 
    }

    /**
     * Return SPOT List
     * Called when we go to the Device List Panel, or we REFRESH the Device List Panel
     * @return mySPOTLIST;
     */
    public Vector returnSPOTList () {
        pause(HEARTBEAT_PERIOD);
        //TODO - try to do deepcopy to pass the list to GUI.
        //synchronized (mySPOTLIST) {
            return mySPOTLIST;
        //}
    }

    /**
     * Configure SPOT
     * Called when we configure the SPOT based on its MAC ID
     * 0.
     * @param myAddress
     * @param sensorList
     * @return true: succeed
     *         false: fail
     */
    //public void configureSPOT (String myAddress, Vector sensorList) {
    public boolean configureSPOT (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            //TODO - can add hashmap
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside configureSPOT: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Get the sensor from the input sensorList, send the "addSensor" Commends to the Listener

                   //TODO - after
                   //myListener.addSensor(ACCELEROMETER_X_SENSOR);

                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside configureSPOT: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }


    /**
     * Start Listener
     * Called when we want the SPOT to start transmitting the data.
     * 0.
     * @param myAddress
     * @return true: succeed
     *         false: fail
     */
    public boolean startListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside startListener: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Start the Listener
                   myListener.start();
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside startListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }
    
    /**
     * Start All the Listeners
     * Called when we want ALL the SPOTs to start transmitting the data.
     * 0. 
     * @return true: succeed
     *         false: fail
     */
    public boolean startAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.start();
            }
        //}
        return true;
    }

    /**
     * Stop Listener
     * Called when we want the SPOT to stop transmitting the data.
     * 0.
     * @param myAddress
     * @return true: succeed
     *         false: fail
     */
    public boolean pauseListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside stopListener: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Start the Listener
                   myListener.doPause();
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside stopListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }

    /**
     * Stop All the Listeners
     * Called when we want ALL the SPOTs to stop transmitting the data.
     * 0.
     * @return true: succeed
     *         false: fail
     */
    public boolean pauseAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.doPause();
            }
        //}
        return true;
    }

    /**
     * Reclaim Listener
     * Called when we finish the session.
     * 0.
     * @param myAddress 
     * @return true: succeed
     *         false: fail
     */
    public boolean stopListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside reclaimListener: Address Matches!");
                   isInTheLIST = true;
                    // Get the Listener
                    Listener myListener = next.getListener();
                    // Start the Listener
                    myListener.doStop();
                    // Reclaim the Port
                   synchronized (PORTBitMap) {
                        PORTBitMap.Clear(next.getPort() - 60);
                   }
                   System.out.println("Inside reclaimListener: Port#" + (next.getPort() - 60) + "is cleared");
                   //Delete the SPOT from the SPOTLIST
                   mySPOTLIST.removeElement(next);
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside reclaimListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }

    /**
     * Reclaim All the Listeners
     * Called when we finish the session.
     * 0.
     * @return true: succeed
     *         false: fail
     */
    public boolean stopAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.doStop();
                // Reclaim the Port
                synchronized (PORTBitMap) {
                    PORTBitMap.Clear(next.getPort() - 60);
                }
                System.out.println("Inside reclaimListener: Port#" + (next.getPort() - 60) + "is cleared");
             // Delete the SPOT from the SPOTLIST
                mySPOTLIST.removeElement(next);
            }
        //}
        return true;
    }
}